
# from yacf.Frontend.C import c_ast
from yacf.Frontend.Shortcuts import getCurrentLanguageAst
from yacf.Frontend.SymbolTable import IdentifierNotFound, ComposedSymbol
c_ast = getCurrentLanguageAst()

from yacf.Frontend.SymbolTable import getSymbolTableFromNode


from yacf.Tools.Tree import NodeNotFound, NodeNotValid, getFileNode

class FilterError(Exception):
    """ Filter error """
    def __init__(self, description):
        self.description = description

    def __str__(self):
        return "FilterError:: " + self.description

    def get_description(self):
        return self.description

class AbstractReverseVisitor(object):
    def __init__(self, condition_func = lambda node : False,):
        self._condition_func = condition_func
        self._rootNode = None
    
    def generic_visit(self, node):
        current = node
        if (not isinstance(current, c_ast.FileAST)) and not self._condition_func(current):
            return self.visit(current.parent)
        elif self._condition_func(current):
            return current
        elif isinstance(current, c_ast.FileAST):
            raise StopIteration
        else:
            raise NodeNotFound('ReverseVisitor failed, node ' + str(self._condition_func.__doc__) + " not found")
        
    def visit(self, node):
        method = 'visit_' + node.__class__.__name__
        visitor = getattr(self, method, self.generic_visit)
        return visitor(node,)

    def iterate(self, ast):
        """ Iterate through matching nodes 

                :param ast: Node to start the search

        """
        # visited_nodes = []
        try:
            while 1:
                # visited_nodes.append(self.visit(ast, ignore = visited_nodes))
                yield self.visit(ast)
        except NodeNotFound:
#            print "    *** Node not found on iterate, will raise StopIteration *** "
#            raise NodeNotFound("Not")
            raise StopIteration

    def apply(self, init_node,):
        """ Searches the node matching the condition_func in the AST
            :param ast: Node to start search
            :param ignore: List of nodes to ignore

            :return: First matching node not in ignore list
        """
        node = None
        self._rootNode = getFileNode(init_node)
        try:
            if not self._condition_func(init_node):
                node = self.generic_visit(init_node, )
            if node and not self._condition_func(node):
                raise NodeNotFound(self._condition_func.__doc__)
            if node:
                return node
        except StopIteration:
            ### TODO: Not sure what to do here...
            raise NodeNotFound('Node not found')
                
        return init_node

class GenericFilterVisitor(object):
   """ Returns the first node validating a condition function

   """

   def __init__(self, condition_func, prev_brother = None):
        self.condition_func = condition_func;
        self.prev_brother = prev_brother;
        self.match = False
        self.parent_of_match = None

   def apply(self, ast, ignore = []):
        """ Searchs the node matching the condition_func in the AST
            
            :param ast: Node to start search
            :param ignore: List of nodes to ignore

            :return: First matching node not in ignore list
    
        """
        self.match = False
        self.ast = ast
        node = ast
        if not self.condition_func(node):
            node = self.generic_visit(ast, ignore = ignore)
        if self.match:
            return node
        if not self.condition_func(node):
            raise NodeNotFound(self.condition_func.__doc__)
        # FIXME: This statement should not be reachable
        return node

   def visit(self, node, prev, offset = 1, ignore = []):
          """ Finds a way to visit a node

                :param node: Node to visit
                :param prev: Syntactically previous node
                :param offset: Not used
                :param ignore: List of nodes to ignore
        
                :return: result of the node visit

          """
          # Continue the search....
          method = 'visit_' + node.__class__.__name__
          visitor = getattr(self, method, self.generic_visit)
          return visitor(node, offset, ignore)
          
   def generic_visit(self, node, offset = 0, ignore = []):
         """ Called if no explicit visitor function exists for a 
              node. Implements preorder (syntactically correct) node visit.
 
                :param node: Node being visited
                :param prev: Syntactically previous node
                :param offset: Not used
                :param ignore: List of nodes to ignore


                :return: Result of the node visit
         """
         # Store the parent node of the match
         debug = False
         iter = node.children().__iter__();
         r = node;
         c = None
         prev = None;
         if debug:
             print " Iterating the childs of node : " + str(node)
             node.show()
             print " Childs : " + str([ n for n in node.children()])
             print " Visiting " + str(node)

         try:
             c = iter.next();
             while not self.condition_func(c) or (self.prev_brother != None and self.prev_brother != prev) or (id(c) in ignore):
                 if self.match:
                    break
                 if debug:
                     print " Act : " + str(c)
                 r = self.visit(c, prev, ignore = ignore)
                 if self.condition_func(r) and (self.prev_brother != None and self.prev_brother == prev) and not self.match and not id(r) in ignore:
                     # Stop iterating, we've found the matching node
                     # Do not execute the else code, we already have in r 
                     # the matching node
                     if debug: print " Act : " + str(c)
                     self.match = True
                     break
                 prev = c
                 c = iter.next()
             else:
                 # r is always the matching node (even if its in the same level)
                 if not id(c) in ignore:
                     r = c
                     self.match = True
                 if debug: print " Level of matching node : " + str(c) + " == " + str(c.name)
         except StopIteration:
             if debug: 
                 print " Stop because : " + str(c)
                 node.show()

         if debug: print " Final node : " + str(r) 
         if debug: print "==" + str(r.name)

         if (self.match == True and self.parent_of_match == None):
              self.parent_of_match = node

         return r

   def parentOfMatch(self):
         """ Parent of the node matched

              .. note::  This is not needed because we have a .parent attribute now

         """
         return self.parent_of_match

   def iterate(self, ast):
         """ Iterate through matching nodes 

                :param ast: Node to start the search

         """
         visited_refs = []
         goal_node = None
         try:
            while 1:
                goal_node = self.apply(ast, ignore = visited_refs)
                # TODO: Check if this is correct
                if (id(goal_node) in visited_refs):
                    raise StopIteration
                visited_refs.append(id(goal_node))
                # visited_nodes.append(self.apply(ast, ignore = visited_nodes))
                # import pdb
                # pdb.set_trace()
                yield goal_node
         except NodeNotFound:
            #print "    *** Node not found on iterate, will raise StopIteration *** "
#            raise NodeNotFound("Not")
            raise StopIteration

   def dfs_iter(self, root, visited = None):
        """ Given a starting node, root, do a depth-first search. 
             .. warning::
                This method does not garantee to transverse the tree on the gramatically correct order

             :return: Last match or raise StopIteration      

        """
        if self.prev_brother != None:
            # Prev brother using DFS won't be the gramatically correct one
            raise NotImplemented

        to_visit = [] 
        if visited is None: visited = set()
        to_visit.append(root) # Start with root
        while len(to_visit) != 0:
            v = to_visit.pop()
            if v not in visited:
              visited.add(v)
#              preorder_process(v)
              if self.condition_func(v):
                  yield v
              to_visit.extend(v.children())
        raise StopIteration



class FilterVisitor(GenericFilterVisitor):
    """ Returns the first node matching the node type
    """

    def __init__(self, match_node_type, prev_brother = None):
         super(FilterVisitor, self).__init__(condition_func = lambda node : type(node) == match_node_type, prev_brother = prev_brother)


class ArrayRefVisitor(GenericFilterVisitor):
    """ Returns the first node matching the node type
    """

    def __init__(self, prev_brother = None):
         super(ArrayRefVisitor, self).__init__(condition_func = lambda node : isinstance(node, c_ast.ArrayRef), prev_brother = prev_brother)

class AnyIDVisitor(GenericFilterVisitor):
    """ Returns the first node matching the node type
    """

    def __init__(self, prev_brother = None):
         super(AnyIDVisitor, self).__init__(condition_func = lambda node : isinstance(node, c_ast.ID), prev_brother = prev_brother)

class AttributeFilter(GenericFilterVisitor):
    """ Returns the first node with the given attribute
    """

    def __init__(self, match_attribute, prev_brother = None):
         super(AttributeFilter, self).__init__(condition_func = lambda node : hasattr(node, match_attribute), prev_brother = prev_brother)


class IDFilter(GenericFilterVisitor):
    """ Returns the first node with an ID
    """

    def __init__(self, id, prev_brother = None):
        def condition(node):
            if (type(node) == c_ast.ID and node.name == id.name):
#                if not hasattr(node, 'sequence') or node.sequence is None:
#                    import pdb
#                    pdb.set_trace()
                return True
            # I am not happy with this
            #===================================================================
            # elif hasattr(node, 'name'):
            #    if type(node.name) == type("") and node.name == id.name:
            #        import pdb
            #        pdb.set_trace()
            #        return True
            #    return False
            # elif hasattr(node, 'declname'):
            #    if type(node.declname) == type("") and node.declname == id.name:
            #        import pdb
            #        pdb.set_trace()
            #        return True
            #    return False
            #===================================================================
            return False
        
        super(IDFilter, self).__init__(condition_func = condition, prev_brother = prev_brother)
#===============================================================================
# 
# class StrFilter(GenericFilterVisitor):
#    """ Returns the first node with a name attr which name is the same as id.name
#    """
# 
#    def __init__(self, id, prev_brother = None):
#         def condition(node):
#              if hasattr(node, 'name'):
#                    return type(node.name) == type("") and node.name == id.name
#              elif hasattr(node, 'declname'):
#                    return type(node.declname) == type("") and node.declname == id.name
#              else:
#                    return False
#         super(StrFilter, self).__init__(condition_func = condition)
#===============================================================================


class DeclFilter(GenericFilterVisitor):
    """ Returns the first node with a Decl
    """

    def __init__(self, attribute, value, prev_brother = None):
         super(DeclFilter, self).__init__(condition_func = lambda node : type(node) == c_ast.Decl and (getattr(node, attribute) == value), prev_brother = prev_brother)

class TypedefFilter(GenericFilterVisitor):
    """ Returns the first TypeDef node with the given name    """

    def __init__(self, name, prev_brother = None):
         super(TypedefFilter, self).__init__(condition_func = lambda node : type(node) == c_ast.Typedef and (getattr(node, 'name') == name), prev_brother = prev_brother)

class StructFilter(GenericFilterVisitor):
    """ Returns the first TypeDef node with the given name    """

    def __init__(self, name, prev_brother = None):
         super(StructFilter, self).__init__(condition_func = lambda node : type(node) == c_ast.Struct and (getattr(node, 'name') == name), prev_brother = prev_brother)


class IdentifierTypeFilter(GenericFilterVisitor):
    """ Returns an IdentifierType  """

    def __init__(self, prev_brother = None):
         super(IdentifierTypeFilter, self).__init__(condition_func = lambda node : type(node) == c_ast.IdentifierType, prev_brother = prev_brother)




class FuncCallFilter(GenericFilterVisitor):
    """ Returns the first node with a FuncCall
    """

    def __init__(self, prev_brother = None):
         # The condition __doc__ is used as exception information
         def condition(node):
             """ FuncCall """
             if hasattr(node, 'name'):
                if node.name == 'cnd':
                    print " name == cnd "
             elif hasattr(node, 'declname'):
                if node.declname == 'cnd':
                    print " Declname == cnd "
             return type(node) == c_ast.FuncCall
         super(FuncCallFilter, self).__init__(condition_func = condition , prev_brother = prev_brother)


class FuncDeclOfNameFilter(GenericFilterVisitor):
    """ Returns a node with a FuncDecl with the specified name
    """

    def __init__(self, name, prev_brother = None):
         # The condition __doc__ is used as exception information
         def condition(node):
              """ Match a specified name for a FuncDecl """
              try:
                  return type(node) == c_ast.FuncDecl and getattr(node.parent, 'name') == name.name
              except AttributeError:
                  return False
         super(FuncDeclOfNameFilter, self).__init__(condition_func = condition , prev_brother = prev_brother)


class FuncDefOfNameFilter(GenericFilterVisitor):
    """ Returns a node with a FuncDecl with the specified name
    """

    def __init__(self, name, prev_brother = None):
         # The condition __doc__ is used as exception information
         def condition(node):
              """ Match a specified name for a FuncDecl """
              try:
                  return type(node) == c_ast.FuncDef and getattr(node.decl, 'name') == name.name
              except AttributeError:
                  return False
         super(FuncDefOfNameFilter, self).__init__(condition_func = condition , prev_brother = prev_brother)

class GlobalTypedefFilter(GenericFilterVisitor):
    """ Returns typedefs defined on the global scope    """

    def __init__(self, prev_brother = None):
         super(GlobalTypedefFilter, self).__init__(condition_func = lambda node : type(node) == c_ast.Typedef
                                                    and type(node.parent) == c_ast.FileAST, prev_brother = prev_brother)



class UndefIDFilter(GenericFilterVisitor):
    """ Returns the first node with a Decl
    """

    def __init__(self, container_node, prev_brother = None):
        st = getSymbolTableFromNode(container_node)
        
        def condition(node):
            if type(node) == c_ast.ID:
                try:
                    symbol = st.lookUp(node)
                except IdentifierNotFound:
                    return False
                #===============================================================
                # import pdb
                # pdb.set_trace()
                #===============================================================
                # If the depth is lower, then it is not declared inside or below
                # print " Symbol depth: " + str(symbol.depth)  + " Container depth: " + str(container_node.depth)
                # Check if it is a struct
                if hasattr(symbol.type.type, 'decl_symbol',):
                    # print " A  partial struct has been found "
                    try:
                        new_symbol = st.lookUp(symbol.type.type.decl_symbol)
                    except IdentifierNotFound:
                        return False
                    symbol = new_symbol
                if symbol.depth < container_node.depth:
                   # Functions are always on the main scope
                   if type(symbol.type) != c_ast.FuncDecl:
                       return True
                 
            return False
        super(UndefIDFilter, self).__init__(condition_func = condition, prev_brother = prev_brother)
